package simple.utils.data;

import com.google.common.base.Preconditions;
import simple.utils.concurrent.NamedReentrantLock;

import java.io.IOException;
import java.util.Map;

/**
 * Created by rehak on 5/13/14.
 */
public class CachedIterableKeyValue<K, V> implements IterableKeyValue<K, V> {

    private final IterableKeyValue<K, V> db;
    private final KeyValueCache<K, V> cache;
    private final NamedReentrantLock lock = new NamedReentrantLock();

    public CachedIterableKeyValue(IterableKeyValue<K, V> db, KeyValueCache<K, V> cache) {
        Preconditions.checkNotNull(cache);
        this.db = db;
        this.cache = cache;
    }

    @Override
    public V get(K k) throws IOException {
        lock.lock(k);
        try {
            V cv = cache.get(k);
            if (cv != null) {
                // cache hit
                return cv;
            } else {
                // cache miss
                V v = db.get(k);
                if (v != null) {
                    cache.put(k, v);
                }
                return v;
            }
        } finally {
            lock.unlock(k);
        }
    }

    @Override
    public void put(K k, V v) throws IOException {
        cache.put(k, v);
        db.put(k, v);
    }

    @Override
    public void delete(K k) throws IOException {
        cache.delete(k);
        db.delete(k);
    }

    @Override
    public long records() throws IOException {
        return db.records();
    }

    @Override
    public SeekingIterator<K, V> iterator() {
        final SeekingIterator<K, V> iterator = db.iterator();
        return new SeekingIterator<K, V>() {

            private K currentKey;

            @Override
            public void seek(K key) {
                iterator.seek(key);
            }

            @Override
            public void close() throws Exception {
                iterator.close();
            }

            @Override
            public boolean hasNext() {
                return iterator.hasNext();
            }

            @Override
            public Map.Entry<K, V> next() {
                Map.Entry<K, V> next = iterator.next();
                cache.put(next.getKey(), next.getValue());
                currentKey = next.getKey();
                return next;
            }

            @Override
            public void remove() {
                iterator.remove();
                cache.delete(currentKey);
            }
        };
    }

    @Override
    public void close() throws Exception {
        db.close();
        cache.close();
    }
}
